﻿using System;
using System.Drawing;
using Spk.Controls.Common;
using System.Drawing.Drawing2D;
using System.Windows.Forms;
using System.Windows.Forms.VisualStyles;
using System.Runtime.InteropServices;

namespace Spk.Controls.Trees
{
    public partial class VirtualTreeView
    {
        // Internal structures ------------------------------------------------

        /// <summary>
        /// Base class responsible for rendering and measuring nodes
        /// </summary>
        /// <remarks>
        /// This class by-design operates on private members of VirtualTreeView
        /// and SHOULD NOT ever be published (nor its descendants).
        /// </remarks>
        private abstract class NodeRenderer : IDisposable
        {
            // Protected structures ------------------------------------------

            /// <summary>
            /// Base class responsible for providing metrics data regarding
            /// tree nodes.
            /// </summary>
            protected abstract class TreeMetrics
            {
                /// <summary>Left margin of the whole tree contents</summary>
                public abstract uint LeftMargin { get; }

                /// <summary>Width of expand mark</summary>
                public abstract uint ExpandMarkWidth { get; }

                /// <summary>Height of expand mark</summary>
                public abstract uint ExpandMarkHeight { get; }

                /// <summary>Margins of expand mark</summary>
                public abstract uint ExpandMarkMargin { get; }

                /// <summary>Width of area needed to display the expand mark
                /// (including its margins)</summary>
                public uint ExpandMarkAreaWidth
                {
                    get
                    {
                        return 2 * ExpandMarkMargin + ExpandMarkWidth;
                    }
                }

                /// <summary>Margin between lines and expand mark 
                /// (subtracted from lines)</summary>
                public abstract uint ExpandMarkLineMargin { get; }

                public abstract uint CheckboxWidth { get; }

                public abstract uint CheckboxHeight { get; }

                public abstract uint CheckboxMargin { get; }

                public uint CheckboxAreaWidth
                {
                    get
                    {
                        return 2 * CheckboxMargin + CheckboxWidth;
                    }
                }

                /// <summary>Additional margin added to each level of tree</summary>
                public abstract uint HorizontalAdditionalLevelMargin { get; }

                /// <summary>Total width of each level margin</summary>
                public uint LevelMargin
                {
                    get
                    {
                        return ExpandMarkAreaWidth + HorizontalAdditionalLevelMargin;
                    }
                }

                /// <summary>Horizontal margin of text</summary>
                public abstract uint TextHMargin { get; }

                /// <summary>Vertical margin of text (used for evaluating default node height)</summary>
                public abstract uint ItemContentsVMargin { get; }

                /// <summary>Left margin of image</summary>
                public abstract uint ImageHMargin { get; }
            }

            /// <summary>
            /// Default tree metrics
            /// </summary>
            protected class DefaultTreeMetrics : TreeMetrics
            {
                public static readonly uint LEFT_MARGIN = 4;
                public static readonly uint EXPAND_MARK_WIDTH = 9;
                public static readonly uint EXPAND_MARK_HEIGHT = 9;
                public static readonly uint EXPAND_MARK_MARGIN = 2;
                public static readonly uint CHECKBOX_WIDTH = 13;
                public static readonly uint CHECKBOX_HEIGHT = 13;
                public static readonly uint CHECKBOX_MARGIN = 3;
                public static readonly uint EXPAND_MARK_LINE_MARGIN = 1;
                public static readonly uint HORIZONTAL_ADDITIONAL_LEVEL_MARGIN = 2;
                public static readonly uint TEXT_H_MARGIN = 3;
                public static readonly uint ITEM_CONTENTS_V_MARGIN = 3;
                public static readonly uint IMAGE_H_MARGIN = 3;

                public override uint LeftMargin
                {
                    get
                    {
                        return LEFT_MARGIN;
                    }
                }

                public override uint ExpandMarkWidth
                {
                    get
                    {
                        return EXPAND_MARK_WIDTH;
                    }
                }

                public override uint ExpandMarkHeight
                {
                    get
                    {
                        return EXPAND_MARK_HEIGHT;
                    }
                }

                public override uint ExpandMarkMargin
                {
                    get
                    {
                        return EXPAND_MARK_MARGIN;
                    }
                }

                public override uint CheckboxWidth
                {
                    get
                    {
                        return CHECKBOX_WIDTH;
                    }
                }

                public override uint CheckboxHeight
                {
                    get
                    {
                        return CHECKBOX_HEIGHT;
                    }
                }

                public override uint CheckboxMargin
                {
                    get
                    {
                        return CHECKBOX_MARGIN;
                    }
                }

                public override uint ExpandMarkLineMargin
                {
                    get
                    {
                        return EXPAND_MARK_LINE_MARGIN;
                    }
                }

                public override uint HorizontalAdditionalLevelMargin
                {
                    get
                    {
                        return HORIZONTAL_ADDITIONAL_LEVEL_MARGIN;
                    }
                }

                public override uint TextHMargin
                {
                    get
                    {
                        return TEXT_H_MARGIN;
                    }
                }

                public override uint ItemContentsVMargin
                {
                    get
                    {
                        return ITEM_CONTENTS_V_MARGIN;
                    }
                }

                public override uint ImageHMargin
                {
                    get
                    {
                        return IMAGE_H_MARGIN;
                    }
                }
            }

            /// <summary>
            /// Tool class providing some helpful methods used during tree node
            /// measuring and rendering.
            /// </summary>
            protected static class RenderTools
            {
                // Internal fields ------------------------------------------------

                internal static readonly uint MINIMUM_TEXT_WIDTH = 16;

                // Private methods -----------------------------------------------

                private static void DrawSelection(Graphics g, NodePaintInfo visual)
                {
                    Color frameColor = SystemColors.MenuHighlight;
                    Color gradientFromColor = Spk.Controls.Common.Drawing.Brighten(frameColor, 80);
                    Color gradientToColor = Spk.Controls.Common.Drawing.Brighten(frameColor, 60);

                    using (Brush brush = new LinearGradientBrush(new Point(visual.Visual.ContentArea.Left, visual.Visual.ContentArea.Top + 1),
                        new Point(visual.Visual.ContentArea.Left, visual.Visual.ContentArea.Bottom - 1),
                        gradientFromColor,
                        gradientToColor))
                        Spk.Controls.Common.Drawing.FillRoundRect(g, brush, visual.Visual.ContentArea, 2);
                    using (Pen pen = new Pen(frameColor))
                        Spk.Controls.Common.Drawing.DrawRoundRect(g, pen, visual.Visual.ContentArea, 2);

                    Rectangle tmpArea = visual.Visual.ContentArea;
                    tmpArea.Inflate(-1, -1);
                    using (Pen pen = new Pen(Color.FromArgb(128, 255, 255, 255)))
                        Spk.Controls.Common.Drawing.DrawRoundRect(g, pen, tmpArea, 2);
                }

                private static void DrawHotTrack(Graphics g, NodePaintInfo visual)
                {
                    Color frameColor = Color.FromArgb(127, SystemColors.MenuHighlight);
                    Color gradientFromColor = Spk.Controls.Common.Drawing.Brighten(frameColor, 80);
                    Color gradientToColor = Spk.Controls.Common.Drawing.Brighten(frameColor, 60);

                    using (Brush brush = new LinearGradientBrush(new Point(visual.Visual.ContentArea.Left, visual.Visual.ContentArea.Top + 1),
                        new Point(visual.Visual.ContentArea.Left, visual.Visual.ContentArea.Bottom - 1),
                        gradientFromColor,
                        gradientToColor))
                        Spk.Controls.Common.Drawing.FillRoundRect(g, brush, visual.Visual.ContentArea, 2);
                    using (Pen pen = new Pen(frameColor))
                        Spk.Controls.Common.Drawing.DrawRoundRect(g, pen, visual.Visual.ContentArea, 2);

                    Rectangle tmpArea = visual.Visual.ContentArea;
                    tmpArea.Inflate(-1, -1);
                    using (Pen pen = new Pen(Color.FromArgb(128, 255, 255, 255)))
                        Spk.Controls.Common.Drawing.DrawRoundRect(g, pen, tmpArea, 2);
                }
                
                // Public methods ------------------------------------------------

                /// <summary>
                /// Draws lines in the treenode using default (internal) theme
                /// </summary>
                public static void DrawLines(VirtualTreeView tree, Graphics g, NodePaintInfo visual, TreeMetrics metrics)
                {
                    if (visual.Visual.Detailed == null)
                        return;

                    if (tree.showLines)
                    {
                        int currentNodeTickY = EvalExpandMarkYCenter(visual.Metrics.NodeArea.Top, visual.Metrics.NodeArea.Height);

                        for (int i = 0; i < visual.General.Lines.Length; i++)
                            using (Pen pen = new Pen(Color.FromArgb(204, 204, 204)))
                            {
                                Rectangle currentLevelRect = new Rectangle(visual.Visual.LineArea.Left + i * (int)metrics.LevelMargin,
                                    visual.Visual.LineArea.Top,
                                    (int)metrics.LevelMargin,
                                    visual.Visual.LineArea.Height);
                                int currentLevelTickX = currentLevelRect.Left + (int)metrics.ExpandMarkAreaWidth / 2;

                                switch (visual.General.Lines[i])
                                {
                                    case TreeLineKinds.OnlyRoot:
                                        {
                                            g.DrawLine(pen, currentLevelTickX, currentNodeTickY, currentLevelRect.Right - 1 - metrics.ExpandMarkLineMargin, currentNodeTickY);

                                            break;
                                        }
                                    case TreeLineKinds.FirstRoot:
                                        {
                                            g.DrawLine(pen, currentLevelTickX, currentNodeTickY, currentLevelRect.Right - 1 - metrics.ExpandMarkLineMargin, currentNodeTickY);
                                            g.DrawLine(pen, currentLevelTickX, currentNodeTickY, currentLevelTickX, currentLevelRect.Bottom - 1);

                                            break;
                                        }
                                    case TreeLineKinds.OnlyInner:
                                        {
                                            g.DrawLine(pen, currentLevelTickX, currentNodeTickY, currentLevelRect.Right - 1 - metrics.ExpandMarkLineMargin, currentNodeTickY);
                                            g.DrawLine(pen, currentLevelTickX, currentLevelRect.Top + metrics.ExpandMarkLineMargin, currentLevelTickX, currentNodeTickY);

                                            break;
                                        }
                                    case TreeLineKinds.FirstInner:
                                        {
                                            g.DrawLine(pen, currentLevelTickX, currentNodeTickY, currentLevelRect.Right - 1 - metrics.ExpandMarkLineMargin, currentNodeTickY);
                                            g.DrawLine(pen, currentLevelTickX, currentLevelRect.Top + metrics.ExpandMarkLineMargin, currentLevelTickX, currentLevelRect.Bottom - 1);

                                            break;
                                        }
                                    case TreeLineKinds.Middle:
                                        {
                                            g.DrawLine(pen, currentLevelTickX, currentNodeTickY, currentLevelRect.Right - 1 - metrics.ExpandMarkLineMargin, currentNodeTickY);
                                            g.DrawLine(pen, currentLevelTickX, currentLevelRect.Top, currentLevelTickX, currentLevelRect.Bottom - 1);

                                            break;
                                        }
                                    case TreeLineKinds.Last:
                                        {
                                            g.DrawLine(pen, currentLevelTickX, currentNodeTickY, currentLevelRect.Right - 1 - metrics.ExpandMarkLineMargin, currentNodeTickY);
                                            g.DrawLine(pen, currentLevelTickX, currentLevelRect.Top, currentLevelTickX, currentNodeTickY);

                                            break;
                                        }
                                    case TreeLineKinds.Line:
                                        {
                                            g.DrawLine(pen, currentLevelTickX, currentLevelRect.Top, currentLevelTickX, currentLevelRect.Bottom - 1);

                                            break;
                                        }
                                    case TreeLineKinds.None:
                                        {
                                            break;
                                        }
                                    default:
                                        throw new InvalidOperationException("Internal error: unsupported line kind!");
                                }
                            }
                    }
                }

                /// <summary>
                /// Draws expand/collapse glyph using default (internal) theme
                /// </summary>
                public static void DrawExpandMark(VirtualTreeView tree, Graphics g, NodePaintInfo visual, TreeMetrics metrics)
                {
                    if (visual.Visual.Detailed == null)
                        return;

                    if (tree.showExpandMarks && visual.General.Node.HasChildren)
                    {
                        Rectangle gradientRect = Spk.Controls.Common.Drawing.CreateGradientRect(visual.Visual.Detailed.ExpandMarkRect);

                        using (Brush brush = new LinearGradientBrush(gradientRect,
                            Color.FromArgb(255, 255, 255),
                            Color.FromArgb(204, 204, 204),
                            90.0f))
                            Spk.Controls.Common.Drawing.FillRoundRect(g, brush, visual.Visual.Detailed.ExpandMarkRect, 2, false);

                        using (Pen pen = new Pen(Color.FromArgb(133, 161, 190)))
                            Spk.Controls.Common.Drawing.DrawRoundRect(g, pen, visual.Visual.Detailed.ExpandMarkRect, 2, false);

                        using (Pen pen = new Pen(SystemColors.WindowText))
                        {
                            Point center = new Point(visual.Visual.Detailed.ExpandMarkRect.Left + visual.Visual.Detailed.ExpandMarkRect.Width / 2,
                                visual.Visual.Detailed.ExpandMarkRect.Top + visual.Visual.Detailed.ExpandMarkRect.Height / 2);

                            if (visual.General.Node.IsExpanded)
                            {
                                g.DrawLine(pen, center.X - 2, center.Y, center.X + 2, center.Y);
                            }
                            else
                            {
                                g.DrawLine(pen, center.X - 2, center.Y, center.X + 2, center.Y);
                                g.DrawLine(pen, center.X, center.Y - 2, center.X, center.Y + 2);
                            }
                        }
                    }
                }

                public static void DrawCheckbox(VirtualTreeView tree, Graphics g, NodePaintInfo visual, TreeMetrics metrics)
                {
                    if (visual.Visual.Detailed == null)
                        return;

                    if (tree.showCheckboxes && visual.General.ShowCheckbox)
                    {
                        // Outer frame

                        Rectangle tempRect = visual.Visual.Detailed.CheckboxRect;
                        tempRect.Width -= 1;
                        tempRect.Height -= 1;

                        using (Pen p = new Pen(Color.FromArgb(133, 161, 190)))
                            g.DrawRectangle(p, tempRect);

                        // Inner frame

                        tempRect.Offset(1, 1);
                        tempRect.Width -= 2;
                        tempRect.Height -= 2;

                        g.DrawRectangle(Pens.White, tempRect);

                        // Fill

                        tempRect.Offset(1, 1);
                        tempRect.Width -= 1;
                        tempRect.Height -= 1;
                        var gradientRect = Spk.Controls.Common.Drawing.CreateGradientRect(tempRect);

                        using (var brush = new LinearGradientBrush(gradientRect,
                            Color.FromArgb(204, 204, 204),
                            Color.FromArgb(255, 255, 255),
                            45))
                        {
                            g.FillRectangle(brush, tempRect);
                        }

                        // Fill border

                        tempRect.Width -= 1;
                        tempRect.Height -= 1;
                        using (var pen = new Pen(Color.FromArgb(16, 0, 0, 0)))
                            g.DrawRectangle(pen, tempRect);

                        // Check mark

                        if (visual.General.Node.IsChecked)
                        {
                            var rect = visual.Visual.Detailed.CheckboxRect;

                            using (Font f = new Font("Marlett", 10))
                            {
                                var checkSize = g.MeasureString("a", f);
                                g.DrawString("a", f, SystemBrushes.WindowText, new PointF(rect.Left + (rect.Width - checkSize.Width) / 2, rect.Top + (rect.Height - checkSize.Height) / 2));
                            }
                        }
                    }
                }

                /// <summary>
                /// Draws contents of node (image, text, selection, focus) using
                /// default (internal) theme
                /// </summary>
                public static void DrawContents(VirtualTreeView tree, Graphics g, NodePaintInfo visual, TreeMetrics metrics)
                {
                    if (visual.Visual.Detailed == null)
                        return;

                    // Selection
                    if (visual.General.Node.IsSelected || visual.Dynamic.MouseSelected)
                    {
                        DrawSelection(g, visual);
                    }
                    else if ((visual.Dynamic.HotTrackElements & HotTrackElements.Contents) != 0)
                    {
                        DrawHotTrack(g, visual);
                    }

                    // Focus
                    if (visual.General.Node.IsFocused && tree.Focused)
                    {
                        using (Pen pen = new Pen(SystemColors.WindowText))
                        {
                            pen.DashStyle = DashStyle.Dot;
                            g.DrawRectangle(pen, visual.Visual.ContentArea.Left + 2,
                                visual.Visual.ContentArea.Top + 2,
                                visual.Visual.ContentArea.Width - 5,
                                visual.Visual.ContentArea.Height - 5);
                        }
                    }

                    // Image
                    if (tree.showImages && tree.images != null && visual.General.ImageIndex >= 0 && visual.General.ImageIndex < tree.images.Images.Count)
                        g.DrawImage(tree.images.Images[visual.General.ImageIndex], visual.Visual.Detailed.ImageRect);

                    // Text

                    StringFormat sf = new StringFormat();
                    sf.Alignment = StringAlignment.Near;
                    sf.LineAlignment = StringAlignment.Center;
                    sf.Trimming = StringTrimming.None;
                    sf.FormatFlags = StringFormatFlags.NoWrap;

                    g.DrawString(visual.General.Text,
                        tree.Font,
                        SystemBrushes.WindowText,
                        visual.Visual.Detailed.TextRect,
                        sf);
                }

                /// <summary>
                /// Evaluates visual info using given rendering metrics.
                /// </summary>
                public static NodePaintInfo.VisualData EvalVisual(VirtualTreeView tree,
                    NodePaintInfo.GeneralData general,
                    NodePaintInfo.MetricsData metrics,
                    TreeMetrics treeMetrics)
                {
                    var drawableArea = tree.drawableArea;

                    // Lines

                    Rectangle lineArea = new Rectangle(metrics.NodeArea.Left + (int)treeMetrics.LeftMargin,
                        metrics.NodeArea.Top,
                        (int)(general.Lines.Length * treeMetrics.LevelMargin),
                        metrics.NodeArea.Height);

                    // Expand mark

                    Rectangle expandMarkArea, expandMarkRect;

                    int checkboxAreaLeft;
                    if (tree.showExpandMarks)
                    {
                        expandMarkArea = new Rectangle(lineArea.Right,
                            metrics.NodeArea.Top,
                            (int)treeMetrics.ExpandMarkAreaWidth,
                            metrics.NodeArea.Height);

                        expandMarkRect = new Rectangle(expandMarkArea.Left + (expandMarkArea.Width - (int)treeMetrics.ExpandMarkWidth) / 2,
                            EvalExpandMarkYCenter(expandMarkArea.Top, expandMarkArea.Height) - (int)treeMetrics.ExpandMarkHeight / 2,
                            (int)treeMetrics.ExpandMarkWidth,
                            (int)treeMetrics.ExpandMarkHeight);

                        checkboxAreaLeft = expandMarkArea.Right;
                    }
                    else
                    {
                        expandMarkArea = Rectangle.Empty;
                        expandMarkRect = Rectangle.Empty;

                        checkboxAreaLeft = lineArea.Right;
                    }

                    // Checkbox

                    Rectangle checkboxArea, checkboxRect;

                    int itemAreaLeft;

                    if (tree.showCheckboxes)
                    {
                        checkboxArea = new Rectangle(checkboxAreaLeft,
                            metrics.NodeArea.Top,
                            (int)treeMetrics.CheckboxAreaWidth,
                            metrics.NodeArea.Height);

                        checkboxRect = new Rectangle(checkboxArea.Left + (checkboxArea.Width - (int)treeMetrics.CheckboxWidth) / 2,
                            EvalExpandMarkYCenter(checkboxArea.Top, checkboxArea.Height) - (int)treeMetrics.CheckboxHeight / 2,
                            (int)treeMetrics.CheckboxWidth,
                            (int)treeMetrics.CheckboxHeight);

                        itemAreaLeft = checkboxArea.Right;
                    }
                    else
                    {
                        checkboxArea = Rectangle.Empty;
                        checkboxRect = Rectangle.Empty;

                        itemAreaLeft = checkboxAreaLeft;
                    }

                    Rectangle itemArea = new Rectangle(itemAreaLeft,
                        metrics.NodeArea.Top,
                        Math.Max(0, metrics.NodeArea.Right - itemAreaLeft),
                        metrics.NodeArea.Height);

                    // Image

                    Rectangle imageArea, imageRect;

                    int textAreaLeft;
                    if (tree.showImages && tree.images != null && general.ImageIndex >= 0 && general.ImageIndex < tree.images.Images.Count)
                    {
                        imageArea = new Rectangle(itemArea.Left,
                            metrics.NodeArea.Top,
                            tree.images.ImageSize.Width + 2 * (int)treeMetrics.ImageHMargin,
                            metrics.NodeArea.Height);

                        imageRect = new Rectangle(imageArea.Left + (imageArea.Width - tree.images.ImageSize.Width) / 2,
                            imageArea.Top + (imageArea.Height - tree.images.ImageSize.Height) / 2,
                            tree.images.ImageSize.Width,
                            tree.images.ImageSize.Height);

                        textAreaLeft = imageArea.Right;
                    }
                    else
                    {
                        imageArea = new Rectangle(0, 0, 0, 0);
                        imageRect = new Rectangle(0, 0, 0, 0);

                        textAreaLeft = itemArea.Left;
                    }

                    // Text

                    Rectangle textRect = new Rectangle(textAreaLeft + (int)treeMetrics.TextHMargin,
                        metrics.NodeArea.Top + (int)treeMetrics.ItemContentsVMargin,
                        Math.Max(0, metrics.NodeArea.Right - textAreaLeft - 2 * (int)treeMetrics.TextHMargin),
                        metrics.NodeArea.Height - 2 * (int)treeMetrics.ItemContentsVMargin);

                    NodePaintInfo.VisualData.DetailedData detailed = new NodePaintInfo.VisualData.DetailedData(
                        checkboxRect,
                        expandMarkRect,
                        imageRect,
                        textRect);
                    NodePaintInfo.VisualData visual = new NodePaintInfo.VisualData(
                        checkboxArea,
                        expandMarkArea,
                        lineArea,
                        itemArea,
                        detailed);

                    return visual;
                }

                public static uint EvalNodeWidth(VirtualTreeView tree, NodePaintInfo.GeneralData general, TreeMetrics treeMetrics)
                {
                    using (Graphics g = tree.CreateGraphics())
                    {
                        SizeF textSize = g.MeasureString(general.Text, tree.Font);

                        uint margin = treeMetrics.LeftMargin;
                        uint linesWidth = ((uint)general.Lines.Length * treeMetrics.LevelMargin);
                        uint expandMarkWidth = (tree.showExpandMarks ? treeMetrics.ExpandMarkAreaWidth : 0);
                        uint checkboxWidth = (tree.showCheckboxes ? treeMetrics.CheckboxAreaWidth : 0);
                        uint imageWidth = tree.showImages && tree.images != null && general.ImageIndex >= 0 && general.ImageIndex < tree.images.Images.Count ?
                            (uint)tree.images.ImageSize.Width + 2 * treeMetrics.ImageHMargin :
                            0;

                        uint textWidth = Math.Max(MINIMUM_TEXT_WIDTH, (uint)textSize.Width + 2 * treeMetrics.TextHMargin);

                        return margin + linesWidth + expandMarkWidth + checkboxWidth + imageWidth + textWidth;
                    }
                }

                public static uint EvalNodeEssentialsWidth(VirtualTreeView tree, NodePaintInfo.GeneralData general, TreeMetrics treeMetrics)
                {
                    uint margin = treeMetrics.LeftMargin;
                    uint linesWidth = ((uint)general.Lines.Length * treeMetrics.LevelMargin);
                    uint expandMarkWidth = (tree.showExpandMarks ? treeMetrics.ExpandMarkAreaWidth : 0);
                    uint checkboxWidth = (tree.showCheckboxes ? treeMetrics.CheckboxAreaWidth : 0);

                    return margin + linesWidth + checkboxWidth + expandMarkWidth;
                }

                // To be deleted
                /// <summary>
                /// Evaluates the y-coordinate of the expand mark's center.
                /// </summary>
                /// <remarks>
                /// Method used to standarize position of the expand marker
                /// among all metods, that require this information.
                /// </remarks>
                public static int EvalExpandMarkYCenter(int areaY, int areaHeight)
                {
                    return areaY + (areaHeight / 2);
                }

                /// <summary>
                /// Evaluates default height of node using default (internal) theme
                /// rendering algorithm.
                /// </summary>
                public static uint EvalDefaultNodeHeight(VirtualTreeView tree, Graphics g, TreeMetrics metrics)
                {
                    int textHeight = (int)g.MeasureString("Wy", tree.Font).Height + 2 * (int)metrics.ItemContentsVMargin;
                    int imageHeight;
                    if (tree.images != null && tree.showImages)
                        imageHeight = tree.images.ImageSize.Height + 2 * (int)metrics.ItemContentsVMargin;
                    else
                        imageHeight = 0;

                    return (uint)Math.Max(textHeight, imageHeight);
                }
            }

            // Protected fields ----------------------------------------------

            /// <summary>
            /// Renderer's host tree.
            /// </summary>
            protected VirtualTreeView tree;

            // Public methods ------------------------------------------------

            /// <summary>
            /// Evaluates height of given node
            /// </summary>
            public abstract uint EvalNodeHeight(VirtualNode node);

            /// <summary>
            /// Returns default height of node
            /// </summary>
            public abstract uint GetDefaultNodeHeight();

            /// <summary>
            /// Draws a single node
            /// </summary>
            public abstract void DrawVisual(Graphics g, Rectangle clipRect, NodePaintInfo visual);

            /// <summary>
            /// Evaluates visual data, that is text, image index and
            /// specific regions for the node to be drawn.
            /// </summary>
            public abstract NodePaintInfo.VisualData EvalVisual(NodePaintInfo.GeneralData general,
                NodePaintInfo.MetricsData metrics);

            public abstract uint EvalNodeWidth(NodePaintInfo.GeneralData general);

            /// <summary>
            /// Notifies renderer, that internal metrics data should
            /// be re-evaluated.
            /// </summary>
            /// <remarks>
            /// Change of tree's font is one of the causes of calling
            /// renderer's invalidate method
            /// </remarks>
            public virtual void Invalidate()
            {

            }

            /// <summary>
            /// Releases any native or managed resources used by renderer.
            /// </summary>
            public virtual void Dispose()
            {

            }

            /// <summary>
            /// Constructor
            /// </summary>
            public NodeRenderer(VirtualTreeView newTree)
            {
                newTree.CheckNull("newTree");
                tree = newTree;
            }
        }

        /// <summary>
        /// Base class for renderers using (possibly partially)
        /// default (internal) rendering and measuring algorithms.
        /// </summary>
        private abstract class GenericNodeRenderer : NodeRenderer
        {
            /// <summary>
            /// Default tree node metrics
            /// </summary>
            protected TreeMetrics defaultMetrics;

            // Protected methods ---------------------------------------------

            /// <summary>
            /// Evaluates default node height
            /// </summary>
            protected virtual void Measure()
            {
                // Node height
                using (Graphics g = tree.CreateGraphics())
                    this.NodeHeight = RenderTools.EvalDefaultNodeHeight(tree, g, defaultMetrics);
            }

            // Protected properties ------------------------------------------

            /// <summary>
            /// Returns default node height
            /// </summary>
            protected uint NodeHeight
            {
                get;
                private set;
            }

            // Public methods ------------------------------------------------

            /// <summary>
            /// Constructor
            /// </summary>
            public GenericNodeRenderer(VirtualTreeView newTree)
                : base(newTree)
            {
                defaultMetrics = new DefaultTreeMetrics();

                Measure();
            }

            public override void Invalidate()
            {
                Measure();
            }
        }

        /// <summary>
        /// Default (internal) renderer.
        /// </summary>
        private class DefaultNodeRenderer : GenericNodeRenderer
        {
            // Public methods ------------------------------------------------

            /// <summary>
            /// Constructor
            /// </summary>
            public DefaultNodeRenderer(VirtualTreeView newTree)
                : base(newTree)
            {

            }

            public override uint EvalNodeHeight(VirtualNode node)
            {
                return NodeHeight;
            }

            public override uint EvalNodeWidth(NodePaintInfo.GeneralData general)
            {
                return RenderTools.EvalNodeWidth(tree, general, defaultMetrics);
            }

            public override void DrawVisual(Graphics g, Rectangle clipRect, NodePaintInfo visual)
            {
                var originalClip = g.Clip;
                
                using (Region tempClipRegion = new Region(clipRect))
                    try
                    {
                        g.Clip = tempClipRegion;

                        RenderTools.DrawLines(tree, g, visual, defaultMetrics);
                        RenderTools.DrawExpandMark(tree, g, visual, defaultMetrics);
                        RenderTools.DrawCheckbox(tree, g, visual, defaultMetrics);
                        RenderTools.DrawContents(tree, g, visual, defaultMetrics);
                    }
                    finally
                    {
                        g.Clip = originalClip;
                    }
            }

            public override NodePaintInfo.VisualData EvalVisual(NodePaintInfo.GeneralData general,
                NodePaintInfo.MetricsData metrics)
            {
                return RenderTools.EvalVisual(tree, general, metrics, defaultMetrics);
            }

            public override uint GetDefaultNodeHeight()
            {
                return NodeHeight;
            }
        }

        /// <summary>
        /// Renderer, which draws default lines and expand mark,
        /// but allows user to draw node's contents.
        /// </summary>
        private class OwnerDrawNodeContentsRenderer : GenericNodeRenderer
        {
            /// <summary>
            /// Constructor
            /// </summary>
            public OwnerDrawNodeContentsRenderer(VirtualTreeView newTree)
                : base(newTree)
            {

            }

            public override uint EvalNodeHeight(VirtualNode node)
            {
                if (tree.GetNodeHeight != null)
                {
                    using (Graphics g = tree.CreateGraphics())
                    {
                        GetNodeHeightEventArgs args = new GetNodeHeightEventArgs(node, g, NodeHeight);
                        tree.OnGetNodeHeight(args);

                        return args.Height;
                    }
                }
                else
                    return NodeHeight;
            }

            public override uint EvalNodeWidth(NodePaintInfo.GeneralData general)
            {
                using (Graphics g = tree.CreateGraphics())
                {
                    GetNodeContentsWidthEventArgs args = new GetNodeContentsWidthEventArgs(general.Node, g, RenderTools.MINIMUM_TEXT_WIDTH);
                    tree.OnGetNodeContentsWidth(args);
                    return RenderTools.EvalNodeEssentialsWidth(tree, general, defaultMetrics) + args.Width;
                }
            }

            public override void DrawVisual(Graphics g, Rectangle clipRect, NodePaintInfo visual)
            {
                var originalClip = g.Clip;

                using (Region tempClipRegion = new Region(clipRect))
                    try
                    {
                        g.Clip = tempClipRegion;

                        RenderTools.DrawLines(tree, g, visual, defaultMetrics);
                        RenderTools.DrawExpandMark(tree, g, visual, defaultMetrics);
                        RenderTools.DrawCheckbox(tree, g, visual, defaultMetrics);

                        if (tree.DrawNodeContents != null)
                        {
                            Rectangle clip = clipRect;
                            clip.Intersect(visual.Visual.ContentArea);
                            g.Clip = new Region(clip);

                            DrawNodeContentsEventArgs args = new DrawNodeContentsEventArgs(visual.General.Node, visual.Visual.ContentArea, g, visual.Dynamic.MouseSelected);
                            tree.OnDrawNodeContents(args);
                        }
                        else
                            RenderTools.DrawContents(tree, g, visual, defaultMetrics);
                    }
                    finally
                    {
                        g.Clip = originalClip;
                    }
            }

            public override NodePaintInfo.VisualData EvalVisual(NodePaintInfo.GeneralData general,
                NodePaintInfo.MetricsData metrics)
            {
                return RenderTools.EvalVisual(tree, general, metrics, defaultMetrics);
            }

            public override uint GetDefaultNodeHeight()
            {
                return NodeHeight;
            }
        }

        /// <summary>
        /// Renderer, which allows user to draw the whole node
        /// by himself.
        /// </summary>
        private class OwnerDrawNodeRenderer : NodeRenderer
        {
            private const uint DefaultNodeHeight = 16;

            /// <summary>
            /// Constructor
            /// </summary>
            public OwnerDrawNodeRenderer(VirtualTreeView newTree)
                : base(newTree)
            {

            }

            public override uint EvalNodeHeight(VirtualNode node)
            {
                if (tree.GetNodeHeight != null)
                {
                    using (Graphics g = tree.CreateGraphics())
                    {
                        GetNodeHeightEventArgs args = new GetNodeHeightEventArgs(node, g, DefaultNodeHeight);
                        tree.OnGetNodeHeight(args);

                        return args.Height;
                    }
                }
                else
                    return DefaultNodeHeight;
            }

            public override uint EvalNodeWidth(NodePaintInfo.GeneralData general)
            {
                if (tree.GetNodeWidth != null)
                {
                    using (Graphics g = tree.CreateGraphics())
                    {
                        GetNodeWidthEventArgs args = new GetNodeWidthEventArgs(general.Node, g, RenderTools.MINIMUM_TEXT_WIDTH);
                        tree.OnGetNodeWidth(args);

                        return args.Width;
                    }
                }
                else
                    return 16;
            }

            public override void DrawVisual(Graphics g, Rectangle clipRect, NodePaintInfo visual)
            {
                if (tree.DrawNode != null)
                {
                    var originalClip = g.Clip;

                    Rectangle clip = clipRect;
                    clip.Intersect(visual.Metrics.NodeArea);

                    using (Region tempClipRegion = new Region(clip))
                        try
                        {
                            g.Clip = tempClipRegion;

                            DrawNodeEventArgs args = new DrawNodeEventArgs(visual.General.Node,
                                visual.Metrics.NodeArea,
                                g,
                                visual.Dynamic.MouseSelected);
                            tree.OnDrawNode(args);
                        }
                        finally
                        {
                            g.Clip = originalClip;
                        }
                }
                else
                {
                    // Visual has empty rects - prepared for manual drawing,
                    // so default methods cannot be used. Drawing strikethrough
                    // rectangle instead.

                    using (Pen p = new Pen(Color.Red))
                    {
                        g.DrawLine(p, visual.Metrics.NodeArea.Left, visual.Metrics.NodeArea.Top, visual.Metrics.NodeArea.Right, visual.Metrics.NodeArea.Bottom);
                        g.DrawLine(p, visual.Metrics.NodeArea.Left, visual.Metrics.NodeArea.Bottom, visual.Metrics.NodeArea.Right, visual.Metrics.NodeArea.Top);
                    }

                    using (Pen p = new Pen(Color.Black))
                        g.DrawRectangle(p, visual.Metrics.NodeArea);
                }
            }

            public override NodePaintInfo.VisualData EvalVisual(NodePaintInfo.GeneralData general,
                NodePaintInfo.MetricsData metrics)
            {
                var drawableArea = tree.drawableArea;

                // Attempt to get regions for node from user
                GetNodeRegionsEventArgs args = new GetNodeRegionsEventArgs(general.Node, metrics.NodeArea);
                tree.OnGetNodeRegions(args);

                return new NodePaintInfo.VisualData(
                    args.CheckboxRectangle,
                    args.ExpandMarkRectangle,
                    Rectangle.Empty,
                    args.ContentsRectangle,
                    null);
            }

            public override uint GetDefaultNodeHeight()
            {
                return DefaultNodeHeight;
            }
        }

        /// <summary>
        /// Renderer, which uses system theme to draw nodes
        /// </summary>
        private class SystemNodeRenderer : NodeRenderer
        {
            // Private structures --------------------------------------------

            private class Native
            {
                [DllImport("gdi32.dll")]
                public static extern IntPtr SelectObject(
                    IntPtr hdc,
                    IntPtr hgdiobj);

                [DllImport("GDI32.dll")]
                public static extern bool DeleteObject(IntPtr objectHandle); 
            }

            private enum TreeViewParts : int
            {
                TreeItem = 1,
                Glyph = 2,
                Branch = 3,
                HotGlyph = 4
            }

            private enum TreeItemStates : int
            {
                Normal = 1,
                Hot = 2,
                Selected = 3,
                Disabled = 4,
                SelectedNoFocus = 5,
                HotSelected = 6
            }

            private enum GlyphStates : int
            {
                Closed = 1,
                Opened = 2
            }

            private enum HotGlyphStates : int
            {
                Closed = 1,
                Opened = 2
            }

            private enum ButtonParts : int
            {
	            BP_PUSHBUTTON = 1,
	            BP_RADIOBUTTON = 2,
	            BP_CHECKBOX = 3,
	            BP_GROUPBOX = 4,
	            BP_USERBUTTON = 5,
	            BP_COMMANDLINK = 6,
	            BP_COMMANDLINKGLYPH = 7
            };

            private enum CheckBoxStates : int
            {
                CBS_UNCHECKEDNORMAL = 1,
                CBS_UNCHECKEDHOT = 2,
                CBS_UNCHECKEDPRESSED = 3,
                CBS_UNCHECKEDDISABLED = 4,
                CBS_CHECKEDNORMAL = 5,
                CBS_CHECKEDHOT = 6,
                CBS_CHECKEDPRESSED = 7,
                CBS_CHECKEDDISABLED = 8,
                CBS_MIXEDNORMAL = 9,
                CBS_MIXEDHOT = 10,
                CBS_MIXEDPRESSED = 11,
                CBS_MIXEDDISABLED = 12,
                CBS_IMPLICITNORMAL = 13,
                CBS_IMPLICITHOT = 14,
                CBS_IMPLICITPRESSED = 15,
                CBS_IMPLICITDISABLED = 16,
                CBS_EXCLUDEDNORMAL = 17,
                CBS_EXCLUDEDHOT = 18,
                CBS_EXCLUDEDPRESSED = 19,
                CBS_EXCLUDEDDISABLED = 20,
            };

            protected class SystemTreeMetrics : TreeMetrics
            {
                private uint leftMargin;
                private uint expandMarkWidth;
                private uint expandMarkHeight;
                private uint expandMarkMargin;
                private uint expandMarkLineMargin;
                private uint checkboxWidth;
                private uint checkboxHeight;
                private uint checkboxMargin;
                private uint horizontalAdditionalLevelMargin;
                private uint textHMargin;
                private uint itemContentsVMargin;
                private uint imageHMargin;

                public SystemTreeMetrics(
                    uint newLeftMargin,
                    uint newExpandMarkWidth,
                    uint newExpandMarkHeight,
                    uint newExpandMarkMargin,
                    uint newExpandMarkLineMargin,
                    uint newCheckboxWidth,
                    uint newCheckboxHeight,
                    uint newCheckboxMargin,
                    uint newHorizontalAdditionalLevelMargin,
                    uint newTextHMargin,
                    uint newItemContentsVMargin,
                    uint newImageHMargin)
                {
                    leftMargin = newLeftMargin;
                    expandMarkWidth = newExpandMarkWidth;
                    expandMarkHeight = newExpandMarkHeight;
                    expandMarkMargin = newExpandMarkMargin;
                    expandMarkLineMargin = newExpandMarkLineMargin;
                    checkboxWidth = newCheckboxWidth;
                    checkboxHeight = newCheckboxHeight;
                    checkboxMargin = newCheckboxMargin;
                    horizontalAdditionalLevelMargin = newHorizontalAdditionalLevelMargin;
                    textHMargin = newTextHMargin;
                    itemContentsVMargin = newItemContentsVMargin;
                    imageHMargin = newImageHMargin;
                }

                public override uint LeftMargin
                {
                    get
                    {
                        return leftMargin;
                    }
                }

                public override uint ExpandMarkWidth
                {
                    get
                    { 
                        return expandMarkWidth; 
                    }
                }

                public override uint ExpandMarkHeight
                {
                    get
                    { 
                        return expandMarkHeight; 
                    }
                }

                public override uint ExpandMarkMargin
                {
                    get 
                    { 
                        return expandMarkMargin; 
                    }
                }

                public override uint ExpandMarkLineMargin
                {
                    get 
                    {
                        return expandMarkLineMargin;
                    }
                }

                public override uint CheckboxWidth
                {
                    get 
                    {
                        return checkboxWidth;
                    }
                }

                public override uint CheckboxHeight
                {
                    get 
                    {
                        return checkboxHeight; 
                    }
                }

                public override uint CheckboxMargin
                {
                    get 
                    {
                        return checkboxMargin;
                    }
                }

                public override uint HorizontalAdditionalLevelMargin
                {
                    get 
                    {
                        return horizontalAdditionalLevelMargin;
                    }
                }

                public override uint TextHMargin
                {
                    get 
                    {
                        return textHMargin;
                    }
                }

                public override uint ItemContentsVMargin
                {
                    get 
                    {
                        return itemContentsVMargin;
                    }
                }

                public override uint ImageHMargin
                {
                    get 
                    {
                        return imageHMargin;
                    }
                }
            }

            // Private fields ------------------------------------------------

            private UXTheme.Context treeThemeContext;
            private UXTheme.Context buttonThemeContext;
            private SystemTreeMetrics treeMetrics;
            private bool useTheme;

            private bool commonMetricsEvaluated;
            private uint nodeContentsHeight;
            private uint nodeHeight;

            // Private methods -----------------------------------------------

            private void Deinit()
            {
                if (treeThemeContext != null)
                    UXTheme.EndWork(treeThemeContext);
                if (buttonThemeContext != null)
                    UXTheme.EndWork(buttonThemeContext);

                treeThemeContext = null;
                useTheme = false;
                commonMetricsEvaluated = false;
            }

            private void Init()
            {
                if (Application.RenderWithVisualStyles)
                {
                    treeThemeContext = UXTheme.BeginWork(tree, "TREEVIEW");
                    buttonThemeContext = UXTheme.BeginWork(tree, "BUTTON");
                    useTheme = (treeThemeContext != null && buttonThemeContext != null);
                }
                else
                {
                    buttonThemeContext = null;
                    treeThemeContext = null;
                    useTheme = false;
                }

                EvalTreeMetrics();
                EvalCommonMetrics();
            }

            private void EvalTreeMetrics()
            {
                if (useTheme)
                {
                    using (Graphics g = tree.CreateGraphics())
                    {
                        uint leftMargin = DefaultTreeMetrics.LEFT_MARGIN;

                        Size expandMarkSize = new Size(0, 0);

                        var parts = new[] { TreeViewParts.Glyph, TreeViewParts.HotGlyph };

                        foreach (TreeViewParts part in parts)
                        foreach (GlyphStates state in Enum.GetValues(typeof(GlyphStates)))
                        {
                            Size size;
                            UXTheme.GetPartSize(treeThemeContext, g, (int)part, (int)state, UXTheme.ThemeSize.TS_DRAW, out size);

                            if (size.Width > expandMarkSize.Width)
                                expandMarkSize.Width = size.Width;
                            if (size.Height > expandMarkSize.Height)
                                expandMarkSize.Height = size.Height;
                        }
                        
                        uint expandMarkMargin = DefaultTreeMetrics.EXPAND_MARK_MARGIN;
                        uint expandMarkLineMargin = DefaultTreeMetrics.EXPAND_MARK_LINE_MARGIN;

                        Size checkboxSize = new Size(0, 0);
                        foreach (CheckBoxStates state in Enum.GetValues(typeof(CheckBoxStates)))
                        {
                            Size size;
                            UXTheme.GetPartSize(buttonThemeContext, g, (int)ButtonParts.BP_CHECKBOX, (int)state, UXTheme.ThemeSize.TS_DRAW, out size);

                            if (size.Width > checkboxSize.Width)
                                checkboxSize.Width = size.Width;
                            if (size.Height > checkboxSize.Height)
                                checkboxSize.Height = size.Height;
                        }

                        uint checkboxMargin = DefaultTreeMetrics.CHECKBOX_MARGIN;
                        uint horizontalAdditionalLevelMargin = 0;
                        uint textHMargin = DefaultTreeMetrics.TEXT_H_MARGIN;
                        uint itemContentsVMargin = DefaultTreeMetrics.ITEM_CONTENTS_V_MARGIN;
                        uint imageHMargin = DefaultTreeMetrics.IMAGE_H_MARGIN;

                        treeMetrics = new SystemTreeMetrics(leftMargin,
                            (uint)expandMarkSize.Width,
                            (uint)expandMarkSize.Height,
                            expandMarkMargin,
                            expandMarkLineMargin,
                            (uint)checkboxSize.Width,
                            (uint)checkboxSize.Height,
                            checkboxMargin,
                            horizontalAdditionalLevelMargin,
                            textHMargin,
                            itemContentsVMargin,
                            imageHMargin);
                    }
                }
                else
                {
                    using (Graphics g = tree.CreateGraphics())
                    {
                        uint leftMargin = DefaultTreeMetrics.LEFT_MARGIN;
                        uint expandMarkWidth = 9;
                        uint expandMarkHeight = 9;
                        uint expandMarkMargin = DefaultTreeMetrics.EXPAND_MARK_MARGIN;
                        uint expandMarkLineMargin = DefaultTreeMetrics.EXPAND_MARK_LINE_MARGIN;

                        Size checkboxSize = new Size(0, 0);
                        foreach (CheckBoxState state in Enum.GetValues(typeof(CheckBoxState)))
                        {
                            Size size = CheckBoxRenderer.GetGlyphSize(g, state);
                            if (size.Width > checkboxSize.Width)
                                checkboxSize.Width = size.Width;
                            if (size.Height > checkboxSize.Height)
                                checkboxSize.Height = size.Height;
                        }

                        uint checkboxMargin = DefaultTreeMetrics.CHECKBOX_MARGIN;
                        uint horizontalAdditionalLevelMargin = DefaultTreeMetrics.HORIZONTAL_ADDITIONAL_LEVEL_MARGIN;
                        uint textHMargin = DefaultTreeMetrics.TEXT_H_MARGIN;
                        uint itemContentsVMargin = DefaultTreeMetrics.ITEM_CONTENTS_V_MARGIN;
                        uint imageHMargin = DefaultTreeMetrics.IMAGE_H_MARGIN;

                        treeMetrics = new SystemTreeMetrics(leftMargin,
                            expandMarkWidth,
                            expandMarkHeight,
                            expandMarkMargin,
                            expandMarkLineMargin,
                            (uint)checkboxSize.Width,
                            (uint)checkboxSize.Height,
                            checkboxMargin,
                            horizontalAdditionalLevelMargin,
                            textHMargin,
                            itemContentsVMargin,
                            imageHMargin);
                    }
                }
            }

            private void EvalCommonMetrics()
            {
                if (useTheme)
                {
                    ThemedEvalNodeHeight(out nodeHeight, out nodeContentsHeight);
                }
                else
                {
                    ClassicEvalNodeHeight(out nodeHeight, out nodeContentsHeight);
                }

                commonMetricsEvaluated = true;
            }

            // Themed methods

            private void ThemedEvalNodeHeight(out uint nodeHeight, out uint contentsHeight)
            {
                using (Graphics g = tree.CreateGraphics())
                {
                    IntPtr hdc = g.GetHdc();
                    IntPtr hFont = tree.Font.ToHfont();
                    Rectangle textRect;

                    try
                    {
                        Native.SelectObject(hdc, hFont);

                        UXTheme.GetTextExtent(treeThemeContext, hdc, (int)TreeViewParts.TreeItem, (int)TreeItemStates.Normal, "Wy", UXTheme.TextFlags.CalcRect, out textRect);
                    }
                    finally
                    {
                        Native.DeleteObject(hFont);
                        g.ReleaseHdc(hdc);
                    }

                    Size imageSize = (tree.showImages && tree.images != null) ? tree.images.ImageSize : Size.Empty;

                    nodeHeight = 2 * treeMetrics.ItemContentsVMargin + Math.Max((uint)textRect.Height, (uint)imageSize.Height);
                    contentsHeight = (uint)textRect.Height;
                }
            }

            private uint ThemedEvalNodeWidth(NodePaintInfo.GeneralData general)
            {
                using (Graphics g = tree.CreateGraphics())
                {
                    Rectangle textRect;

                    IntPtr hdc = g.GetHdc();
                    IntPtr hFont = tree.Font.ToHfont();
                    try
                    {
                        Native.SelectObject(hdc, hFont);

                        UXTheme.GetTextExtent(treeThemeContext,
                            hdc,
                            (int)TreeViewParts.TreeItem,
                            (int)TreeItemStates.Normal,
                            general.Text,
                            UXTheme.TextFlags.CalcRect | UXTheme.TextFlags.SingleLine,
                            out textRect);
                    }
                    finally
                    {
                        Native.DeleteObject(hFont);
                        g.ReleaseHdc(hdc);
                    }

                    return treeMetrics.LeftMargin +
                        (uint)general.Lines.Length * treeMetrics.LevelMargin +
                        (tree.showExpandMarks ? treeMetrics.ExpandMarkAreaWidth : 0) +
                        (tree.showCheckboxes ? treeMetrics.CheckboxAreaWidth : 0) +
                        (tree.showImages && tree.images != null && general.ImageIndex >= 0 && general.ImageIndex < tree.images.Images.Count ? (uint)tree.images.ImageSize.Width + 2 * treeMetrics.ImageHMargin : 0) +
                        (uint)textRect.Width + 2 * treeMetrics.TextHMargin;
                }
            }

            private void ThemedDrawVisual(Graphics g, Rectangle clipRect, NodePaintInfo visual)
            {
                if (!commonMetricsEvaluated)
                    throw new InvalidOperationException("Internal error: attempt to draw tree item part without common metrics!");

                Region originalClip = g.Clip;

                using (Region tempClipRegion = new Region(clipRect))
                {
                    g.Clip = tempClipRegion;

                    ThemedDrawLines(g, clipRect, visual);
                    ThemedDrawExpandMark(g, clipRect, visual);
                    ThemedDrawCheckbox(g, clipRect, visual);
                    ThemedDrawContents(g, clipRect, visual);

                    g.Clip = originalClip;
                }
            }

            private void ThemedDrawLines(Graphics g, Rectangle cliprect, NodePaintInfo visual)
            {
                if (!commonMetricsEvaluated)
                    throw new InvalidOperationException("Internal error: attempt to draw tree item part without common metrics!");
                if (visual.Visual.Detailed == null)
                    throw new InvalidOperationException("Internal error: attempt to draw tree item part without detailed data!");

                RenderTools.DrawLines(tree, g, visual, treeMetrics);
            }

            private void ThemedDrawExpandMark(Graphics g, Rectangle clipRect, NodePaintInfo visual)
            {
                if (!commonMetricsEvaluated)
                    throw new InvalidOperationException("Internal error: attempt to draw tree item part without common metrics!");
                if (visual.Visual.Detailed == null)
                    throw new InvalidOperationException("Internal error: attempt to draw tree item part without detailed data!");

                if (tree.showExpandMarks && visual.General.Node.HasChildren)
                {
                    int part, state;

                    if (visual.General.Node.IsExpanded)
                    {
                        if ((visual.Dynamic.HotTrackElements & HotTrackElements.ExpandMark) != 0)
                        {
                            part = (int)TreeViewParts.HotGlyph;
                            state = (int)HotGlyphStates.Opened;
                        }
                        else
                        {
                            part = (int)TreeViewParts.Glyph;
                            state = (int)GlyphStates.Opened;
                        }
                    }
                    else
                    {
                        if ((visual.Dynamic.HotTrackElements & HotTrackElements.ExpandMark) != 0)
                        {
                            part = (int)TreeViewParts.HotGlyph;
                            state = (int)HotGlyphStates.Closed;
                        }
                        else
                        {
                            part = (int)TreeViewParts.Glyph;
                            state = (int)GlyphStates.Closed;
                        }
                    }

                    UXTheme.DrawBackground(treeThemeContext, g, part, state, visual.Visual.Detailed.ExpandMarkRect, clipRect);
                }
            }

            private void ThemedDrawCheckbox(Graphics g, Rectangle clipRect, NodePaintInfo visual)
            {
                if (!commonMetricsEvaluated)
                    throw new InvalidOperationException("Internal error: attempt to draw tree item part without common metrics!");
                if (visual.Visual.Detailed == null)
                    throw new InvalidOperationException("Internal error: attempt to draw tree item part without detailed data!");

                if (tree.showCheckboxes && visual.General.ShowCheckbox)
                {
                    int part, state;

                    if ((visual.Dynamic.HotTrackElements & HotTrackElements.Checkbox) != 0)
                    {
                        if (visual.General.Node.IsChecked)
                        {
                            part = (int)ButtonParts.BP_CHECKBOX;
                            state = (int)CheckBoxStates.CBS_CHECKEDHOT;
                        }
                        else
                        {
                            part = (int)ButtonParts.BP_CHECKBOX;
                            state = (int)CheckBoxStates.CBS_UNCHECKEDHOT;
                        }
                    }
                    else
                    {
                        if (visual.General.Node.IsChecked)
                        {
                            part = (int)ButtonParts.BP_CHECKBOX;
                            state = (int)CheckBoxStates.CBS_CHECKEDNORMAL;
                        }
                        else
                        {
                            part = (int)ButtonParts.BP_CHECKBOX;
                            state = (int)CheckBoxStates.CBS_UNCHECKEDNORMAL;
                        }
                    }

                    UXTheme.DrawBackground(buttonThemeContext, g, part, state, visual.Visual.Detailed.CheckboxRect, clipRect);
                }
            }

            private void ThemedDrawContents(Graphics g, Rectangle clipRect, NodePaintInfo visual)
            {
                if (!commonMetricsEvaluated)
                    throw new InvalidOperationException("Internal error: attempt to draw tree item part without common metrics!");
                if (visual.Visual.Detailed == null)
                    throw new InvalidOperationException("Internal error: attempt to draw tree item part without detailed data!");

                int part, state;
                part = (int)TreeViewParts.TreeItem;
                bool drawBackground;

                // Selection / hottrack
                if (visual.General.Node.IsSelected || visual.Dynamic.MouseSelected)
                {
                    if ((visual.Dynamic.HotTrackElements & HotTrackElements.Contents) == 0)
                    {
                        state = (int)TreeItemStates.Selected;
                        drawBackground = true;
                    }
                    else
                    {
                        state = (int)TreeItemStates.HotSelected;
                        drawBackground = true;
                    }
                }
                else 
                {
                    if ((visual.Dynamic.HotTrackElements & HotTrackElements.Contents) == 0)
                    {
                        state = (int)TabItemState.Normal;
                        drawBackground = false;
                    }
                    else
                    {
                        state = (int)TabItemState.Hot;
                        drawBackground = true;
                    }
                }

                if (drawBackground)
                    UXTheme.DrawBackground(treeThemeContext, g, part, state, visual.Visual.ContentArea, clipRect);

                // Image
                if (tree.showImages && tree.images != null && visual.General.ImageIndex >= 0 && visual.General.ImageIndex < tree.images.Images.Count)
                    g.DrawImage(tree.images.Images[visual.General.ImageIndex], visual.Visual.Detailed.ImageRect);

                // Text

                IntPtr hdc = g.GetHdc();
                IntPtr hFont = tree.Font.ToHfont();
                try
                {
                    Native.SelectObject(hdc, hFont);

                    Rectangle textRect = visual.Visual.Detailed.TextRect;
                    UXTheme.DrawText(treeThemeContext,
                        hdc,
                        part,
                        state,
                        visual.General.Text, UXTheme.TextFlags.Left | UXTheme.TextFlags.Vcenter | UXTheme.TextFlags.SingleLine,
                        ref textRect);
                }
                finally
                {
                    Native.DeleteObject(hFont);
                    g.ReleaseHdc(hdc);
                }
            }

            // Classic methods

            private void ClassicEvalNodeHeight(out uint nodeHeight, out uint contentsHeight)
            {
                using (Graphics g = tree.CreateGraphics())
                {
                    SizeF size = g.MeasureString("Wy", tree.Font);

                    Size imageSize = (tree.showImages && tree.images != null) ? tree.images.ImageSize : Size.Empty;

                    nodeHeight = 2 * treeMetrics.ItemContentsVMargin + Math.Max((uint)size.Height, (uint)imageSize.Height);
                    contentsHeight = (uint)size.Height;
                }
            }

            private uint ClassicEvalNodeWidth(NodePaintInfo.GeneralData general)
            {
                using (Graphics g = tree.CreateGraphics())
                {
                    SizeF textSize = g.MeasureString(general.Text, tree.Font);

                    return treeMetrics.LeftMargin +
                        (uint)general.Lines.Length * treeMetrics.LevelMargin +
                        (tree.showExpandMarks ? treeMetrics.ExpandMarkAreaWidth : 0) +
                        (tree.showCheckboxes ? treeMetrics.CheckboxAreaWidth : 0) +
                        (tree.showImages && tree.images != null && general.ImageIndex >= 0 && general.ImageIndex < tree.images.Images.Count ? (uint)tree.images.ImageSize.Width + 2 * treeMetrics.ImageHMargin : 0) +
                        (uint)textSize.Width + 2 * treeMetrics.TextHMargin;
                }
            }

            private void ClassicDrawVisual(Graphics g, Rectangle clipRect, NodePaintInfo visual)
            {
                if (!commonMetricsEvaluated)
                    throw new InvalidOperationException("Internal error: attempt to draw tree item part without common metrics!");

                Region originalClip = g.Clip;

                using (Region tempClipRegion = new Region(clipRect))
                {
                    g.Clip = tempClipRegion;

                    ClassicDrawLines(g, clipRect, visual);
                    ClassicDrawExpandMark(g, clipRect, visual);
                    ClassicDrawCheckbox(g, clipRect, visual);
                    ClassicDrawContents(g, clipRect, visual);

                    g.Clip = originalClip;
                }
            }

            private void ClassicDrawLines(Graphics g, Rectangle clipRect, NodePaintInfo visual)
            {
                if (!commonMetricsEvaluated)
                    throw new InvalidOperationException("Internal error: attempt to draw tree item part without common metrics!");
                if (visual.Visual.Detailed == null)
                    throw new InvalidOperationException("Internal error: attempt to draw tree item part without detailed data!");
                
                RenderTools.DrawLines(tree, g, visual, treeMetrics);
            }

            private void ClassicDrawExpandMark(Graphics g, Rectangle clipRect, NodePaintInfo visual)
            {
                if (!commonMetricsEvaluated)
                    throw new InvalidOperationException("Internal error: attempt to draw tree item part without common metrics!");
                if (visual.Visual.Detailed == null)
                    throw new InvalidOperationException("Internal error: attempt to draw tree item part without detailed data!");

                if (visual.General.Node.HasChildren)
                {
                    g.FillRectangle(Brushes.White, visual.Visual.Detailed.ExpandMarkRect);
                    g.DrawRectangle(Pens.Black, Spk.Controls.Common.Drawing.ConvertToApiRect(visual.Visual.Detailed.ExpandMarkRect));

                    int halfX = visual.Visual.Detailed.ExpandMarkRect.Left + (visual.Visual.Detailed.ExpandMarkRect.Width / 2);
                    int halfY = visual.Visual.Detailed.ExpandMarkRect.Top + (visual.Visual.Detailed.ExpandMarkRect.Height / 2);

                    g.DrawLine(Pens.Black,
                        new Point(visual.Visual.Detailed.ExpandMarkRect.Left + 2, halfY),
                        new Point(visual.Visual.Detailed.ExpandMarkRect.Right - 3, halfY));

                    if (!visual.General.Node.IsExpanded)
                        g.DrawLine(Pens.Black,
                            new Point(halfX, visual.Visual.Detailed.ExpandMarkRect.Top + 2),
                            new Point(halfX, visual.Visual.Detailed.ExpandMarkRect.Bottom - 3));
                }                
            }

            private void ClassicDrawCheckbox(Graphics g, Rectangle clipRect, NodePaintInfo visual)
            {
                if (!commonMetricsEvaluated)
                    throw new InvalidOperationException("Internal error: attempt to draw tree item part without common metrics!");
                if (visual.Visual.Detailed == null)
                    throw new InvalidOperationException("Internal error: attempt to draw tree item part without detailed data!");

                if (tree.showCheckboxes && visual.General.ShowCheckbox)
                {
                    if (visual.General.Node.IsChecked)
                        CheckBoxRenderer.DrawCheckBox(g, visual.Visual.Detailed.CheckboxRect.Location, CheckBoxState.CheckedNormal);
                    else
                        CheckBoxRenderer.DrawCheckBox(g, visual.Visual.Detailed.CheckboxRect.Location, CheckBoxState.UncheckedNormal);
                }
            }

            private void ClassicDrawContents(Graphics g, Rectangle clipRect, NodePaintInfo visual)
            {
                if (!commonMetricsEvaluated)
                    throw new InvalidOperationException("Internal error: attempt to draw tree item part without common metrics!");
                if (visual.Visual.Detailed == null)
                    throw new InvalidOperationException("Internal error: attempt to draw tree item part without detailed data!");

                bool selected = visual.General.Node.IsSelected || visual.Dynamic.MouseSelected;

                // Selection
                if (selected)
                {
                    g.FillRectangle(SystemBrushes.Highlight, visual.Visual.ContentArea);
                }

                // Focus
                if (visual.General.Node.IsFocused && tree.Focused)
                {
                    Rectangle focusRectangle = new Rectangle(visual.Visual.ContentArea.Left + 2,
                        visual.Visual.ContentArea.Top + 2,
                        visual.Visual.ContentArea.Width - 4,
                        visual.Visual.ContentArea.Height - 4);

                    if (selected)
                        ControlPaint.DrawFocusRectangle(g, focusRectangle, SystemColors.HighlightText, SystemColors.Highlight);
                    else
                        ControlPaint.DrawFocusRectangle(g, focusRectangle, SystemColors.HighlightText, SystemColors.Highlight);
                }

                // Image
                if (tree.showImages && tree.images != null && visual.General.ImageIndex >= 0 && visual.General.ImageIndex < tree.images.Images.Count)
                    g.DrawImage(tree.images.Images[visual.General.ImageIndex], visual.Visual.Detailed.ImageRect);

                // Text
                Brush brush = (visual.General.Node.IsSelected || visual.Dynamic.MouseSelected) ? SystemBrushes.HighlightText : SystemBrushes.WindowText;

                StringFormat sf = new StringFormat();
                sf.Alignment = StringAlignment.Near;
                sf.LineAlignment = StringAlignment.Center;
                sf.Trimming = StringTrimming.None;
                sf.FormatFlags = StringFormatFlags.NoWrap;

                g.DrawString(visual.General.Text,
                    tree.Font,
                    brush,
                    visual.Visual.Detailed.TextRect,
                    sf);
            }

            // Public structures ---------------------------------------------

            public enum SystemTheme
            {
                Default,
                Explorer
            }

            // Public methods ------------------------------------------------

            public SystemNodeRenderer(VirtualTreeView newTree)
                : base(newTree)
            {
                treeMetrics = null;

                commonMetricsEvaluated = false;

                Init();
            }

            public void SetTheme(SystemTheme theme)
            {
                switch (theme)
                {
                    case SystemTheme.Explorer:
                        {
                            UXTheme.SetTheme(tree, "Explorer");
                            EvalTreeMetrics();
                            break;
                        }
                    case SystemTheme.Default:
                        {
                            UXTheme.SetTheme(tree, "");
                            EvalTreeMetrics();
                            break;
                        }
                    default:
                        throw new InvalidOperationException("Not supported system theme!");
                }
            }

            public override void Dispose()
            {
                Deinit();
            }

            public override void Invalidate()
            {
                Deinit();
                Init();

                base.Invalidate();
            }

            public override uint EvalNodeHeight(VirtualNode node)
            {
                return nodeHeight;
            }

            public override uint GetDefaultNodeHeight()
            {
                return nodeHeight;
            }

            public override void DrawVisual(Graphics g, Rectangle clipRect, NodePaintInfo visual)
            {
                if (useTheme)
                    ThemedDrawVisual(g, clipRect, visual);
                else
                    ClassicDrawVisual(g, clipRect, visual);
            }

            public override NodePaintInfo.VisualData EvalVisual(NodePaintInfo.GeneralData general, NodePaintInfo.MetricsData metrics)
            {
                return RenderTools.EvalVisual(tree, general, metrics, treeMetrics);
            }

            public override uint EvalNodeWidth(NodePaintInfo.GeneralData general)
            {
                if (useTheme)
                    return ThemedEvalNodeWidth(general);
                else
                    return ClassicEvalNodeWidth(general);
            }
        }
    }
}